package com.sdjenkins.util;

import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Properties helper object that provides:
 * <ol>
 * <li>Loading of properties from external file</li>
 * <li>Initialise object's instance variables from properties</li>
 * <li>Optional checking above instance variables are all set (not null)</li>
 * </ol>
 * 
 * Instance variables must be prefixed with "<b>property_</b>" to qualify. If a "<b>.</b>" is used in the property name the helper will attempt to map to an equivalent variable that uses "<b>_</b>".<br>
 * For example: property <b>my.test</b> and <b>my_test</b> will both map to var <b>property_my_test</b>
 *  
 * <br>Inherited variables are also included.<br>
 * <br>
 * 
 * @author sjenkins
 *
 */

public class PropertyHelper {

	private String PROP_PREFIX = "property_";
	private Logger log;
	
	public PropertyHelper(Logger log) {
		this.log = log;
	}
	
	public Properties getPropertiesFromSource(Object source) {
		
		Properties props = new Properties();
		Reader rdr = null;
		try {
			if (source instanceof File) {
				rdr = new FileReader(source.toString()); 
			}
			else {
				rdr = new StringReader(source.toString().replaceAll(";", "\n"));
			}
			props.load(rdr);
			log.info("["+source+"] : loaded");
		}
		catch (Exception e) {
			String msg = "Cannot load properties from: ["+source+"] ";
			log.severe(msg+e);
			throw new RuntimeException(msg, e);
		}		
		return props;
	}	

	public void initVarsFromProperties(Object targetObject, Properties props, boolean... strict) {
		
		Collection<Class<?>> classes = new ArrayList<Class<?>>();
		Field[] fields;
		boolean isStrict = strict.length==0 ? false : strict[0]; 
		classes = getSuperClasses(targetObject.getClass(), classes);
		for (Class<?> cl : classes) {
			log.info("checking: "+cl.toString());
			fields = cl.getDeclaredFields();
			for (Field f : fields) {
				checkField(f, targetObject, props, isStrict);
			}
		}		
		log.info("checking: complete");
	}
	
	
	private void checkField(Field fld, Object target, Properties props, boolean isStrict) {
		if (fld.getType().equals(String.class)
				&& fld.getName().toLowerCase().startsWith(PROP_PREFIX)) {
			setFromProperty(fld, target, props, isStrict);
		}

	}

	private void setFromProperty(Field fld, Object target, Properties props, boolean isStrict) {
		
		String fname = fld.getName();
		String name = fname.toLowerCase().substring(PROP_PREFIX.length());
		String value = props.getProperty(name);
		Object oldValue = getFieldValue(fld, target);
		if (value == null) {
			name = name.replace('_', '.');
			value = props.getProperty(name);
		}
		if (value == null && isStrict && oldValue == null) {
			throw new RuntimeException("ERROR: Instance variable ("+fld.getName()+") is not set");
		}
		else {
			setField(fld, target, fname, name, value, oldValue);
		}

	}

	private Object getFieldValue(Field fld, Object target) {
		
		Object value = null;
		try {
			value = fld.get(target);
		}
		catch (Exception e) {
			throw new RuntimeException("ERROR: Cannot get instance variable value ("+fld.getName()+")", e);
		}
		return value;
	}

	private void setField(Field fld, Object target, String fname, String name, String value, Object oldValue) {
		
		String msg = "["+fname+" = ";
		Level lvl = oldValue!=null || value==null ? Level.WARNING : Level.INFO;
		if (value == null) {
			msg += oldValue+"] (no new value found)";
		}
		else {
			try {
				fld.set(target, value);
				msg += value+( oldValue==null ? "]" : "] (was: "+oldValue+")");  
			} 
			catch (Exception e) {
				throw new RuntimeException("ERROR: Cannot set instance variable ("+fld.getName()+")", e);
			}
		}
		log.log(lvl, msg);
	}

	
	
	private Collection<Class<?>> getSuperClasses(Class<?> cl,
			Collection<Class<?>> c) {

		c.add(cl);
		Class<?> sc = cl.getSuperclass();
		if (sc != null) {
			c = getSuperClasses(sc, c);
		}
		return c;
	}

}
